#include "OneNETSDK.h"
#include <PubSubClient.h>
#include "ibase64.h"
#include <MD5Builder.h>
#define CHECK_INTERVAL 15000
#define MESSAGE_BUFFER_SIZE 5
#define DEBUG
static const char *deviceName = NULL;
static const char *productKey = NULL;
static const char *deviceSecret = NULL;

struct DeviceProperty
{
  String key;
  String value;
};

DeviceProperty PropertyMessageBuffer[MESSAGE_BUFFER_SIZE];

#define MQTT_PORT 1883

#define DATA_CALLBACK_SIZE 20

#define OLINK_BODY_FORMAT "{\"id\":\"123\",\"version\":\"1.0\",\"params\":%s}"
#define OLINK_EVENT_BODY_FORMAT "{\"id\": \"123\",\"version\": \"1.0\",\"params\": %s,\"method\": \"thing.event.%s.post\"}"

static unsigned long lastMs = 0;

static PubSubClient *client = NULL;

char OneNETSDK::clientId[256] = "";
char OneNETSDK::mqttUsername[100] = "";
char OneNETSDK::mqttPwd[256] = "";
char OneNETSDK::domain[150] = "";

char OneNETSDK::OLINK_TOPIC_PROP_POST[150] = "";
char OneNETSDK::OLINK_TOPIC_PROP_POST_REPLY[150] = "";  //订阅属性上报结果
char OneNETSDK::OLINK_TOPIC_PROP_SET[150] = "";
char OneNETSDK::OLINK_TOPIC_EVENT[150] = "";
//HMAC-MD5
void hmac_md5(byte key[], int keyLength, byte msg[], int msgLength, byte result[]) {
  int blockSize = 64;
  byte baKey[64] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
                   };
  byte hash_result[16];
  byte baOuterKeyPadded[blockSize];
  byte baInnerKeyPadded[blockSize];
  byte tempHash[16];
  MD5Builder md5{};
  if (keyLength > blockSize) {
    md5.begin();
    md5.add(key, keyLength);
    md5.calculate();
    md5.getBytes(baKey);
  } else {
    for (int i = 0; i < keyLength; i++) {
      baKey[i] = key[i];
    }
  }
  for (int i = 0; i < blockSize; i++) {
    baOuterKeyPadded[i] = baKey[i] ^ 0x5C;
    baInnerKeyPadded[i] = baKey[i] ^ 0x36;
  }
  // return hash(o_key_pad ∥ hash(i_key_pad ∥ message)) // Where ∥ is concatenation
  md5.begin();
  md5.add(baInnerKeyPadded, blockSize);
  md5.add(msg, msgLength);
  md5.calculate();
  md5.getBytes(tempHash);
  md5.begin();
  md5.add(baOuterKeyPadded, blockSize);
  md5.add(tempHash, 16);
  md5.calculate();
  md5.getBytes(hash_result);
  memcpy(result, hash_result, 16);
}
//URL编码,计算密码用 20220502新的URLENCODE函数，ESP32可用
inline String urlEncode(const char *msg)
{
    const char *hex = "0123456789ABCDEF";
    String encodedMsg = "";
    while (*msg != '\0')
    {
        if (('a' <= *msg && *msg <= 'z') || ('A' <= *msg && *msg <= 'Z') || ('0' <= *msg && *msg <= '9') || *msg == '-' || *msg == '_' || *msg == '.' || *msg == '~')
        {
            encodedMsg += *msg;
        }
        else
        {
            encodedMsg += '%';
            encodedMsg += hex[*msg >> 4];
            encodedMsg += hex[*msg & 0xf];
        }
        msg++;
    }
    return encodedMsg;
}
inline String urlEncode(String msg)
{
    return urlEncode(msg.c_str());
}
//byte转换十六进制字段
static void parmPass(JsonVariant parm) {
  //    const char *method = parm["method"];
  for (int i = 0; i < DATA_CALLBACK_SIZE; i++)
  {
    if (poniter_array[i].key) {
      bool hasKey = parm["params"].containsKey(poniter_array[i].key);
      if (hasKey) poniter_array[i].fp(parm["params"]);
    }
  }
}
// 所有云服务的回调都会首先进入这里，例如属性下发
static void callback(char *topic, byte *payload, unsigned int length) {
#ifdef DEBUG
  Serial.print(F("CMD="));
  payload[length] = '\0';
  Serial.println((char *)payload);
#endif
  if (strstr(topic, OneNETSDK::OLINK_TOPIC_PROP_SET)) {
    StaticJsonDocument<200> doc;
    DeserializationError error = deserializeJson(doc, payload); //反序列化JSON数据
    if (!error) parmPass(doc.as<JsonVariant>());//检查反序列化是否成功,将参数传递后打印输出
  }
}

static bool mqttConnecting = false;
//MQTT连接尝试和检查函数
void OneNETSDK::mqttCheckConnect() {
  if (client != NULL && !mqttConnecting) {
    if (!client->connected()) {
      client->disconnect();
#ifdef DEBUG
      Serial.print(F("OneNET."));
#endif
      mqttConnecting = true;
      if (client->connect(clientId, mqttUsername, mqttPwd)) {
#ifdef DEBUG
        Serial.println(F("Connected!"));
#endif
        // client->subscribe(OLINK_TOPIC_PROP_POST_REPLY, 1); //订阅属性上报结果
        client->subscribe(OLINK_TOPIC_PROP_SET, 1);      //订阅属性下发数据
      }
#ifdef DEBUG 
      else 
      {
        Serial.print(F("错误代码=")); 
        Serial.println(client->state());
      }
#endif
        mqttConnecting = false;
    }
  }
}

//MQTT初始化及token计算主函数
void OneNETSDK::begin(Client &ethClient,
                      const char *_productKey,
                      const char *_deviceName,
                      const char *_deviceSecret
                     )
{
  client = new PubSubClient(ethClient);
  client->setBufferSize(1024);      //20220502新增
  client->setKeepAlive(30);         //20220502新增
  productKey = _productKey;       //产品ID
  deviceName = _deviceName;       //设备名称
  deviceSecret = _deviceSecret;   //设备密钥
  const char *timestamp = "1798732799";           //时间戳1798732799=2026-12-31 23:59:59
  char b64Dekey[32];
  char signtxt[100];
  char hmac_buf[16];
  char token[32];
  b64_decode(b64Dekey, (char *)deviceSecret, strlen(deviceSecret));
  snprintf(signtxt, 100, "%s\nmd5\nproducts/%s/devices/%s\n2018-10-31", timestamp, productKey, deviceName);
  hmac_md5((unsigned char *)b64Dekey, strlen(b64Dekey), (unsigned char *)signtxt, strlen(signtxt), (unsigned char *)hmac_buf);
  b64_encode(token, hmac_buf, 16);
  String tokenkey = urlEncode(token);       //20220502新的URLENCODE函数，ESP32可用
  snprintf(mqttPwd, 256, "version=2018-10-31&res=products%%2F%s%%2Fdevices%%2F%s&et=%s&method=md5&sign=%s", productKey, deviceName, timestamp, tokenkey.c_str());
  strncpy(clientId, deviceName, 16);           //OneNET 设备名=clientId
  strncpy(mqttUsername, productKey, 16);       //OneNET productID=productKey
  snprintf(OLINK_TOPIC_PROP_POST, 150, "$sys/%s/%s/thing/property/post", productKey, deviceName);
  snprintf(OLINK_TOPIC_PROP_POST_REPLY, 150, "$sys/%s/%s/thing/property/post/reply", productKey, deviceName); //订阅属性上报结果
  snprintf(OLINK_TOPIC_PROP_SET, 150, "$sys/%s/%s/thing/property/set", productKey, deviceName);               //订阅属性下发数据
  snprintf(OLINK_TOPIC_EVENT, 150, "$sys/%s/%s/thing/event", productKey, deviceName);
  // strncpy(domain, "studio-mqtt.heclouds.com", 32);  //OneNET Studio 非加密接口，官方已宣布放弃OneNET Studio
  strncpy(domain, "mqtts.heclouds.com", 32);           //OneNET物联网开放平台地址，新版本都需要用这个
  client->setServer(domain, MQTT_PORT); /* 连接WiFi之后，连接MQTT服务器 */
  client->setCallback(callback);
  mqttCheckConnect();                   //MQTT连接尝试和检查函数
}

void OneNETSDK::loop() {        //MQTT持续链接函数
  client->loop();
  if (millis() - lastMs >= CHECK_INTERVAL) {
    lastMs = millis();
    mqttCheckConnect();               // MQTT连接尝试和检查函数
    messageBufferCheck();             // 检查是否有数据需要发送
  }
}

void OneNETSDK::sendEvent(const char *eventId, const char *param)
{
    char topicKey[156];
    sprintf(topicKey, "%s/%s/post", OLINK_TOPIC_EVENT, eventId);
    char jsonBuf[1024];
    sprintf(jsonBuf, OLINK_EVENT_BODY_FORMAT, param, eventId);
    boolean d = client->publish(topicKey, jsonBuf);
#ifdef DEBUG
    if (d) Serial.println(F("publish:0 成功"));
#endif
}
void OneNETSDK::sendEvent(const char *eventId)
{
    sendEvent(eventId, "{}");
}

unsigned long lastSendMS = 0;

// 检查是否有数据需要发送
void OneNETSDK::messageBufferCheck() {
  int bufferSize = 0;
  for (int i = 0; i < MESSAGE_BUFFER_SIZE; i++) {
    if (PropertyMessageBuffer[i].key.length() > 0) bufferSize++;
  }
  if (bufferSize > 0) {
    if (bufferSize >= MESSAGE_BUFFER_SIZE) {
      sendBuffer();
    } else {
      unsigned long nowMS = millis();
      if (nowMS - lastSendMS > 5000) {     // 5s 发送一次数据
        sendBuffer();
        lastSendMS = nowMS;
      }
    }
  }
}

// 发送 buffer 数据
void OneNETSDK::sendBuffer() {
  int i;
  String buffer;
  for (i = 0; i < MESSAGE_BUFFER_SIZE; i++)  {
    if (PropertyMessageBuffer[i].key.length() > 0) {
      buffer += "\"" + PropertyMessageBuffer[i].key + R"(":{"value":)" + PropertyMessageBuffer[i].value + "},";
      PropertyMessageBuffer[i].key = "";
      PropertyMessageBuffer[i].value = "";
    }
  }
  buffer = "{" + buffer.substring(0, buffer.length() - 1) + "}";
  send(buffer.c_str());
}

void addMessageToBuffer(char *key, const String& value) {
  int i;
  for (i = 0; i < MESSAGE_BUFFER_SIZE; i++) {
    if (PropertyMessageBuffer[i].key.length() == 0) {
      PropertyMessageBuffer[i].key = key;
      PropertyMessageBuffer[i].value = value;
      break;
    }
  }
}
void OneNETSDK::send(const char *param) {
  char jsonBuf[1024];
  sprintf(jsonBuf, OLINK_BODY_FORMAT, param);
  boolean d = client->publish(OLINK_TOPIC_PROP_POST, jsonBuf);
#ifdef DEBUG
  if (d) Serial.println(param);
#endif
}
void OneNETSDK::send(char *key, float number) {
  addMessageToBuffer(key, String(number));
  messageBufferCheck();
}
void OneNETSDK::send(char *key, int number) {
  addMessageToBuffer(key, String(number));
  messageBufferCheck();
}
void OneNETSDK::send(char *key, long number) {
  addMessageToBuffer(key, String(number));
  messageBufferCheck();
}
void OneNETSDK::send(char *key, double number) {
  addMessageToBuffer(key, String(number));
  messageBufferCheck();
}
void OneNETSDK::send(char *key, char *text) {
  if (0 == strcmp("true",text) or 0 == strcmp("false",text)) {
    addMessageToBuffer(key, String(text));
  } else {
    addMessageToBuffer(key, "\"" + String(text) + "\"");
  }
  messageBufferCheck();
}

int OneNETSDK::bindData(char *key, poniter_fun fp) {
  int i;
  for (i = 0; i < DATA_CALLBACK_SIZE; i++) {
    if (!poniter_array[i].fp) {
      poniter_array[i].key = key;
      poniter_array[i].fp = fp;
      return 0;
    }
  }
  return -1;
}

int OneNETSDK::unbindData(char *key) {
  int i;
  for (i = 0; i < DATA_CALLBACK_SIZE; i++) {
    if (!strcmp(poniter_array[i].key, key)) {
      poniter_array[i].key = NULL;
      poniter_array[i].fp = NULL;
      return 0;
    }
  }
  return -1;
}

boolean OneNETSDK::publish(const char *topic, const char *payload, bool retained){
    return client->publish(topic, payload, retained);
}

boolean OneNETSDK::publish(const char *topic, const char *payload){
    return client->publish(topic, payload);
}

boolean OneNETSDK::subscribe(char* topic, uint8_t qos, poniter_fun fp){
    boolean ret = false;
    if(client->subscribe(topic, qos)){
        ret = true;
        bindData(topic, fp);
        // Serial.print(F("subcribe:"));
        // Serial.println(topic);
    }
    return ret;
}

boolean OneNETSDK::subscribe(char* topic, poniter_fun fp){
    return subscribe(topic, 0, fp);
}

boolean OneNETSDK::unsubscribe(char* topic){
    boolean ret = false;
    if(client->unsubscribe(topic)){
        ret = true;
        unbindData(topic);
        // Serial.print(F("unsubcribe:"));
        // Serial.println(topic);
    }
    return ret;
}